
import dom.Writer;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;


class Main {

  private String umlDocName =
    "/home/keith/IPFW/acs562/trunk/utilities/UML2DocBook/dist/umldoc.xml";
  private File diagramDocDir;
//  private static String umlDocName = "C:/SVN/acs562/utilities/UML2DocBook/dist/umldoc.xml";
//  private static String diagramDocDirName = "C:/SVN/acs562/utilities/stateofplay/bouml/";
  private static List<String> indents = new ArrayList<String>();
  private final String lineSep = "\n"; //System.getProperty( "line.separator" );
  private NumberFormat numFmt = NumberFormat.getIntegerInstance();
  private Document umlDoc;
  private Element docRoot;
  private Element usecases;
  private Element usecasediagrams;
  private Element classdiagrams;
  private Element sequencediagrams;
  private Element communicationDiagrams;
  private Element domainmodels;
  private Element classes;


  private Main( String xmlOutFileName, File imgDirName ) throws
    ParserConfigurationException {
    numFmt.setMinimumIntegerDigits( 2 );

    DocumentBuilderFactory docBldFac = DocumentBuilderFactory.newInstance();
    DocumentBuilder docBld = docBldFac.newDocumentBuilder();
    umlDoc = docBld.newDocument();
    docRoot = umlDoc.createElement( "bouml" );
    umlDoc.appendChild( docRoot );

    usecases = umlDoc.createElement( "usecases" );
    docRoot.appendChild( umlDoc.createTextNode( lineSep ) );
    docRoot.appendChild( usecases );

    usecasediagrams = umlDoc.createElement( "usecasediagrams" );
    docRoot.appendChild( umlDoc.createTextNode( lineSep ) );
    docRoot.appendChild( usecasediagrams );

    domainmodels = umlDoc.createElement( "domainmodels" );
    docRoot.appendChild( umlDoc.createTextNode( lineSep ) );
    docRoot.appendChild( domainmodels );

    classdiagrams = umlDoc.createElement( "classdiagrams" );
    docRoot.appendChild( umlDoc.createTextNode( lineSep ) );
    docRoot.appendChild( classdiagrams );

    sequencediagrams = umlDoc.createElement( "sequencediagrams" );
    docRoot.appendChild( umlDoc.createTextNode( lineSep ) );
    docRoot.appendChild( sequencediagrams );

    communicationDiagrams = umlDoc.createElement( "communicationdiagrams" );
    docRoot.appendChild( umlDoc.createTextNode( lineSep ) );
    docRoot.appendChild( communicationDiagrams );

    classes = umlDoc.createElement( "classes" );
    docRoot.appendChild( umlDoc.createTextNode( lineSep ) );
    docRoot.appendChild( classes );

    this.diagramDocDir = imgDirName;
    this.umlDocName = xmlOutFileName;
  }


  private void dump() throws IOException {
    final Writer writer = new Writer();
    final File umlDocFile = new File( umlDocName );

    final java.io.Writer fileWriter = new BufferedWriter( new FileWriter(
      umlDocFile ) );
    writer.setOutput( fileWriter );

    Node node = umlDoc;

    if ( node != null ) {
      writer.write( node );
    }

    UmlCom.trace( "xml UML document written to " + umlDocFile.getCanonicalPath() );
  }


  private static String getIndent( int level ) {
    final String indentSpacing = "  ";
    String indent = null;

    if ( level < indents.size() ) {
      indent = indents.get( level );
    } else if ( level == 0 ) {
      indent = "";
      indents.add( indent );
    } else {
      indent = getIndent( level - 1 ) + indentSpacing;
      indents.add( indent );
    }

    return indent;
  }


  private <T> Element createElement( String name, T value ) {
    Element newElem = umlDoc.createElement( name );

    newElem.appendChild( umlDoc.createTextNode( value.toString() ) );

    return newElem;
  }


  private Element processUmlItemDescription( String description ) {
    final Element descriptionElem = umlDoc.createElement( "description" );
    if ( description != null && description.startsWith( "<" ) ) {
      final Element errorElem = umlDoc.createElement( "error" );
      final Element textElem = umlDoc.createElement( "text" );
      final StringBuilder sb = new StringBuilder(
        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" );

      sb.append( "<root>\n" );
      sb.append( description );
      sb.append( "</root>\n" );
      textElem.appendChild( umlDoc.createCDATASection( sb.toString() ) );
      try {
        DocumentBuilderFactory docBldFac = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBld = docBldFac.newDocumentBuilder();
        InputStream is = new ByteArrayInputStream( sb.toString().getBytes() );
        Document textDoc = docBld.parse( is );
        NodeList children = textDoc.getDocumentElement().getChildNodes();
        for ( int idx = 0, len = children.getLength(); idx < len; idx++ ) {
          descriptionElem.appendChild(
            umlDoc.importNode( children.item( idx ), true ) );
        }
//        resultElem.appendChild(umlDoc.createTextNode("doc parsed!"));
//        descriptionElem.appendChild(errorElem);
      } catch ( Exception ex ) {
//        Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
        errorElem.appendChild( umlDoc.createTextNode( ex.toString() ) );
        descriptionElem.appendChild( errorElem );
        descriptionElem.appendChild( textElem );
      }

    } else {
      Element mainElem = umlDoc.createElement( "main" );
      Element preElem = umlDoc.createElement( "pre" );

      mainElem.appendChild( preElem );
      preElem.appendChild( umlDoc.createTextNode( description ) );
      descriptionElem.appendChild( mainElem );
    }
    return descriptionElem;
  }


  private Element visitItem( Element parent, String tagName, UmlItem umlItem ) {
    final Element itemElem = umlDoc.createElement( tagName );
    parent.appendChild( umlDoc.createTextNode( lineSep ) );
    parent.appendChild( itemElem );

    final Element id = createElement( "id", umlItem.getIdentifier() );
    itemElem.appendChild( umlDoc.createTextNode( lineSep ) );
    itemElem.appendChild( id );

    final Element attrName = createElement( "name", umlItem.name() );
    itemElem.appendChild( umlDoc.createTextNode( lineSep ) );
    itemElem.appendChild( attrName );

    itemElem.appendChild( umlDoc.createTextNode( lineSep ) );
    itemElem.appendChild( processUmlItemDescription( umlItem.description() ) );

    return itemElem;
  }


  private String visibilitySymbol(UmlBaseClassMember umlClassMember ){
    final String symbol;

    switch ( umlClassMember.visibility().value() ){
      case aVisibility._PublicVisibility:
        symbol = "+";
        break;
      case aVisibility._PrivateVisibility:
        symbol = "-";
        break;
      case aVisibility._ProtectedVisibility:
        symbol = "#";
        break;
      case aVisibility._PackageVisibility: /* intentional fall through */
      default:
        symbol = "~";
        break;
    }

    return symbol;
  }


  private void classMember( Element parent, UmlBaseClassMember umlClassMember ){

    final Element staticElem = createElement( "static", umlClassMember.isClassMember() );
    parent.appendChild( umlDoc.createTextNode( lineSep ) );
    parent.appendChild( staticElem );
  }


  private void visit( Element parent, UmlAttribute umlAttribute ) {
    final Element attribute = visitItem( parent, "attribute", umlAttribute );

    final Element attrType = createElement( "type", umlAttribute.type() );
    attribute.appendChild( umlDoc.createTextNode( lineSep ) );
    attribute.appendChild( attrType );

    final Element visibility = createElement( "visibility", visibilitySymbol( umlAttribute ) );
    attribute.appendChild( umlDoc.createTextNode( lineSep ) );
    attribute.appendChild( visibility );
    
    classMember( attribute, umlAttribute);
  }


  private void visit( Element parent, UmlOperation umlOperation ) {
    final Element operation = visitItem( parent, "operation", umlOperation );

    final Element attrType = createElement( "return_type", umlOperation.returnType() );
    operation.appendChild( umlDoc.createTextNode( lineSep ) );
    operation.appendChild( attrType );

    final Element visibility = createElement( "visibility", visibilitySymbol( umlOperation ) );
    operation.appendChild( umlDoc.createTextNode( lineSep ) );
    operation.appendChild( visibility );

    classMember(operation, umlOperation);

    UmlParameter[] parms = umlOperation.params();
    final StringBuilder sb = new StringBuilder();

    for ( int idx = 0, len = ( parms == null ? 0 : parms.length ); idx < len; idx++ ){
      if ( idx > 0 ){
        sb.append( ", ");
      }

      UmlParameter parm = parms[ idx ];
      sb.append( parm.name );
      if ( parm.type != null ){
        sb.append( ": " );
        sb.append( parm.type );
      }
    }
    final Element decl = createElement( "parm_list", sb.toString() );
    operation.appendChild( umlDoc.createTextNode( lineSep ) );
    operation.appendChild( decl );
  }


  private void visit( Element parent, UmlRelation umlRelation ) {
    final Element relation = visitItem( parent, "relation", umlRelation );

//    final Element getOper = createElement( "get_operation", umlRelation.getOperation() );
//    relation.appendChild( umlDoc.createTextNode( lineSep ) );
//    relation.appendChild( getOper );

    final String roleName = umlRelation.roleName();
    final Element decl = createElement( "role_name", roleName );
    relation.appendChild( umlDoc.createTextNode( lineSep ) );
    relation.appendChild( decl );

    final Element visibility = createElement( "visibility", visibilitySymbol( umlRelation ) );
    relation.appendChild( umlDoc.createTextNode( lineSep ) );
    relation.appendChild( visibility );
try {
    final UmlClass roleType = umlRelation.roleType();
    if ( roleType != null ){
      final Element type = createElement( "type", roleType.name() );
      relation.appendChild( umlDoc.createTextNode( lineSep ) );
      relation.appendChild( type );
    }
} catch ( Exception ex ){
       final Element error = createElement( "error", ex.getLocalizedMessage() );
      relation.appendChild( umlDoc.createTextNode( lineSep ) );
      relation.appendChild( error );
}
    final Element stereotype = createElement( "stereotype", umlRelation.stereotype() );
    relation.appendChild( umlDoc.createTextNode( lineSep ) );
    relation.appendChild( stereotype );

    classMember(relation, umlRelation);
  }


  private void visit( UmlClass umlClass ) {
    final Element elem = visitItem( classes, "class", umlClass );

    final Element stereotype = createElement( "stereotype", umlClass.stereotype() );
    elem.appendChild( umlDoc.createTextNode( lineSep ) );
    elem.appendChild( stereotype );

    final Element children = umlDoc.createElement( "children" );
    elem.appendChild( umlDoc.createTextNode( lineSep ) );
    elem.appendChild( children );

    for ( UmlItem child : umlClass.children() ) {
      final String kind;
      switch ( child.kind().value() ) {
        case anItemKind._anOperation:
          kind = "operation";
          visit( children, (UmlOperation) child );
          break;
        case anItemKind._anAttribute:
          kind = "attribute";
          visit( children, (UmlAttribute) child );
          break;
        case anItemKind._aRelation:
          kind = "relation";
          visit( children, (UmlRelation) child );
          break;
        default:
          kind = "{unknown:" + child.kind().value() + "}";
          final Element childName = createElement( "child_name", child.name() );
          children.appendChild( umlDoc.createTextNode( lineSep ) );
          children.appendChild( childName );

          final Element childKind = createElement( "child_kind", kind );
          children.appendChild( umlDoc.createTextNode( lineSep ) );
          children.appendChild( childKind );
      }
//child.
    }
  }


  private void visit( UmlUseCase umlUseCase ) {
    final Element elem = visitItem( usecases, "usecase", umlUseCase );
  }


  private File process( UmlDiagram diagram ) {

    final String baseFileName =
      Integer.toString( diagram.getIdentifier() ) + ".png";
    final File f = new File( diagramDocDir, baseFileName );
//              "/home/keith/IPFW/acs562/trunk/utilities/stateofplay/bouml/" + Integer.
//              toString(docRoot.getIdentifier()) + ".png";
//        "/home/keith/temp/bouml/" + Integer.toString( docRoot.getIdentifier() ) + ".png";

    diagram.saveIn( f.getAbsolutePath() );
    if ( f.exists() ) {
      UmlCom.trace( ":file: " + f.getAbsolutePath() + ", size: " + Long.toString(
        f.length() ) );
    }

    return f;
  }


  private Element processDiagram( String name, Element parent, UmlDiagram umlDiagram ) {
    final Element elem = visitItem( parent, name, umlDiagram );

    File f = process( umlDiagram );

    elem.appendChild( umlDoc.createTextNode( lineSep ) );

    elem.appendChild( createElement( "image", f.getName() ) );
    elem.appendChild( umlDoc.createTextNode( lineSep ) );

//    UmlCom.trace("&lt;figure id=\"f" + umlUseCaseDiagram.getIdentifier() + "\"&gt;");
//    UmlCom.trace("&lt;title&gt;" + umlUseCaseDiagram.name() + "&lt;/title&gt;");
//    UmlCom.trace("&lt;mediaobject id=\"bouml-" + f.getName() + "\"&gt;");
//    UmlCom.trace("&lt;imageobject&gt;");
//    UmlCom.trace("&lt;imagedata width=\"5.5in\" fileref=\"" + "bouml/" + f.getName() + "\"/&gt;");
//    UmlCom.trace("&lt;/imageobject&gt;");
//    UmlCom.trace("&lt;/mediaobject&gt;");
//    UmlCom.trace("&lt;/figure&gt;");
    return elem;
  }


  private void visit( UmlClassDiagram umlclassDiagram ) {
    UmlItem item = umlclassDiagram.parent();

    if ( "domain".equals( item.stereotype() ) ) {
      processDiagram( "domainmodel", domainmodels, umlclassDiagram );
    } else {
      processDiagram( "classdiagram", classdiagrams, umlclassDiagram );
    }
  }


  private void visit( UmlUseCaseDiagram umlUseCaseDiagram ) {
    processDiagram( "usecasediagram", usecasediagrams, umlUseCaseDiagram );
  }


  private void visit( UmlSequenceDiagram umlSequenceDiagram ) {
    processDiagram( "sequencediagram", sequencediagrams, umlSequenceDiagram );
  }


  private void visit( UmlCollaborationDiagram umlCommunicationDiagram ) {
    processDiagram( "communicationdiagram", communicationDiagrams, umlCommunicationDiagram );
  }


  public void visit( UmlItem root, final String spacing, final String levelTxt ) {
    UmlCom.trace(
      spacing + levelTxt + ":---------------------------------------" );
    UmlCom.trace( spacing + levelTxt + ":name: " + root.name() );
    UmlCom.trace( spacing + levelTxt + ":id: " + root.getIdentifier() );
    UmlCom.trace( spacing + levelTxt + ":class: " + root.getClass().getName() );
    UmlCom.trace( spacing + levelTxt + ":description: " + root.description() );
    UmlCom.trace( spacing + levelTxt + ":kind: " + root.kind().value() );
    UmlCom.trace( spacing + levelTxt + ":stereotype: " + root.stereotype() );

//    if ( root instanceof UmlDiagram ) {
//      final UmlDiagram diagram = (UmlDiagram) root;
//
//      final String diagramFileName =
//        Integer.toString( root.getIdentifier() ) + ".png";
////              "/home/keith/IPFW/acs562/trunk/utilities/stateofplay/bouml/" + Integer.
////              toString(docRoot.getIdentifier()) + ".png";
////        "/home/keith/temp/bouml/" + Integer.toString( docRoot.getIdentifier() ) + ".png";
//
//      final File f = new File( diagramDocDir, diagramFileName );
//      diagram.saveIn( f.getAbsolutePath() );
//      if ( f.exists() ) {
//        UmlCom.trace( spacing + levelTxt + ":file: " + diagramFileName +
//                      ", size: " + Long.toString( f.length() ) );
////         <title>Team Logo</title>
////        <mediaobject id="logo.png">
////            <imageobject>
////                <imagedata fileref="DD-Logo.png" />
////            </imageobject>
////        </mediaobject>
//        UmlCom.trace( "&lt;figure id=\"f" + root.getIdentifier() + "\"&gt;" );
//        UmlCom.trace( "&lt;title&gt;" + root.name() + "&lt;/title&gt;" );
//        UmlCom.trace( "&lt;mediaobject id=\"bouml-" + f.getName() + "\"&gt;" );
//        UmlCom.trace( "&lt;imageobject&gt;" );
//        UmlCom.trace( 
//          "&lt;imagedata width=\"5.5in\" fileref=\"" + "bouml/" + f.getName() + "\"/&gt;" );
//        UmlCom.trace( "&lt;/imageobject&gt;" );
//        UmlCom.trace( "&lt;/mediaobject&gt;" );
//        UmlCom.trace( "&lt;/figure&gt;" );
//      }
//    }
  }


  public void visitChildren( UmlItem root, int level, boolean designPkg ) {
    final String spacing = getIndent( level );
    final String levelTxt = numFmt.format( level );

    if ( root instanceof UmlUseCase ) {
      visit( (UmlUseCase) root );
    } else if ( root instanceof UmlUseCaseDiagram ) {
      visit( (UmlUseCaseDiagram) root );
    } else if ( root instanceof UmlClassDiagram ) {
      visit( (UmlClassDiagram) root );
    } else if ( root instanceof UmlSequenceDiagram ) {
      visit( (UmlSequenceDiagram) root );
    } else if ( root instanceof UmlCollaborationDiagram ) {
      visit( (UmlCollaborationDiagram) root );
    } else if ( root instanceof UmlClass ) {
      if ( designPkg ) {
        visit( (UmlClass) root );
      }
    } else {
//      visit( root, spacing, levelTxt );
    }
    boolean ignorePkg = ( root instanceof UmlPackage &&
      "Removed Stuff".equalsIgnoreCase( root.name() ) );

    if ( ! designPkg && ( root instanceof UmlPackage ) ){
      designPkg = ( "Design Phase".equalsIgnoreCase( root.name() ) );
    }

//    if ( !ignore && root.kind().value() != anItemKind._aClass ) {
    if ( !ignorePkg ) {
      for ( UmlItem child : root.children() ) {
        visitChildren( child, level + 1, designPkg );
      }
    }
  }


  /**
   *  the program is called with the socket port number in argument
   */
  public static void main( String argv[] ) {

    if ( argv.length == 1 ) {
      UmlCom.connect( Integer.valueOf( argv[0] ).intValue() );

      UmlItem target = UmlCom.targetItem();
      try {
//          target.set_dir(argv.length - 1, argv);
        String curDir = System.getProperty( "user.dir" );
        UmlCom.trace( "curDir: " + curDir );

        Hashtable props = UmlBasePackage.getProject().properties();
        UmlCom.trace( "properties: " + props.toString() );

        final String xmlOutFileName = System.getenv( "bouml_out" );
        final String imgDirName = System.getenv( "bouml_img_dir" );
        File imgDir = new File( imgDirName );

        UmlCom.trace( "Image directory: " + imgDir.getAbsolutePath() );
        if ( !imgDir.exists() ) {
          UmlCom.trace(
            "Specified image directory \"" + imgDirName + "\" does not exist." );
        } else if ( !imgDir.isDirectory() ) {
          UmlCom.trace(
            "Specified image directory \"" + imgDirName + "\" is not a directory." );
        } else {

          Main main = new Main( xmlOutFileName, imgDir );

          main.visitChildren( target, 0, false );

          main.dump();
        }
        UmlCom.trace( "done." );
        throw new UnsupportedOperationException( "Not yet implemented" );
      // does something on the target, here suppose that a virtual
      // operation exist at UmlItem level (and probably sub_level !)
//        UmlCom.targetItem().???();
      } catch ( Exception ex ) {
        UmlCom.trace( ex.toString() );
      } finally {
        // must be called to cleanly inform that all is done
        UmlCom.bye();
        UmlCom.close();
      }
    }
    System.exit( 0 );
  }
}
